package icasue.stronger.wareable.logicstronger;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.lang.Nullable;

import javax.validation.constraints.NotNull;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2020/6/28 下午4:54
 * @UpdateDate:
 * @Description:
 */
@Data
@ToString
@NoArgsConstructor
public abstract class StrongerAdvice<K,V> {

    /**
     * defined ThreadLocal to supply any proxy,any method,any thread a invoke bucket(java.util.Map),
     * through this field,wu can record some important infos before current thread had bind proxy's method invoke
     * and deal some extra preInvoke logic, and postInvoke logic.
     */
    public ThreadLocal<Map<K, V>> processBuffer = new ThreadLocal<>();
    public List<BeforeMethodLogic> beforeMethodLogicChain = new ArrayList<>();
    public List<AfterMethodLogic> afterMethodLogicChain = new ArrayList<>();
    public List<ExceptionMethodHandleLogic> exceptionMethodHandleLogicChain = new ArrayList<>();


    public Object executeBeforeMethod(Class<?> proxyClass, Object invoker, Method method, Object... params) throws Throwable {
        for (BeforeMethodLogic beforeMethodLogic : beforeMethodLogicChain) {
            beforeMethodLogic.executeBeforeMethod(proxyClass, invoker, method,params);
        }
        return null;
    };

    public Object executeAfterMethod(@Nullable Object result,
                                     Class<?> proxyClass, Object invoker, Method method, Object... params) throws Throwable {
        for (AfterMethodLogic afterMethodLogic : afterMethodLogicChain) {
            afterMethodLogic.executeAfterMethod(result,proxyClass, invoker, method,params);
        }
        return null;
    };

    public Object executeExceptionMethodHandles(@NotNull Throwable error,@Nullable Object result,
                                     Class<?> proxyClass, Object invoker, Method method, Object... params) throws Throwable {
        ExceptionMethodHandleLogic choosePreferHandle = null;

        for (ExceptionMethodHandleLogic exceptionMethodHandleLogic : exceptionMethodHandleLogicChain) {
            Class supportErrorType = exceptionMethodHandleLogic.support();
            if(supportErrorType == null){
                exceptionMethodHandleLogic.handleMethodException(error,result,proxyClass, invoker, method,params);
            }else if(supportErrorType.isInstance(error)){
                choosePreferHandle = choosePreferHandle == null ? exceptionMethodHandleLogic :
                        (supportErrorType.isAssignableFrom(choosePreferHandle.support()) ? choosePreferHandle
                                : exceptionMethodHandleLogic);
            }
        }
        if(choosePreferHandle != null){
            choosePreferHandle.handleMethodException(error,result,proxyClass, invoker, method,params);
        }else {
            throw error;
        }
        return null;
    };


    @FunctionalInterface
    public interface BeforeMethodLogic{
        /**
         * Support an FunctionInterface,wu can define special beforeMethodInvoke logic,
         * through this export point,we can get method's invoke infos,virtual invoker,and proxy's virtual reference.
         * @param proxyClass  proxy's virtual reference
         * @param invoker     virtual invoker,spring beanDefinition or other beanDefinitions came from other framework,
         *                    contains original features.
         * @param method      the method which prepare invoke.
         * @param params      the params which method invoke need.
         * @return
         */
         Object executeBeforeMethod(Class<?> proxyClass, Object invoker, Method method, Object... params) throws Throwable;
    }

    @FunctionalInterface
    public interface AfterMethodLogic{
        /**
         * Support an FunctionInterface,wu can define special postMethodInvoke logic,
         * through this export point,we can get method's invoke infos,virtual invoker,invoke result,and proxy's virtual reference.
         * @param result        this method's invoked result.
         * @param proxyClass    proxy's virtual reference
         * @param invoker       virtual invoker,spring beanDefinition or other beanDefinitions came from other framework,
         *                      contains original features.
         * @param method        the method which prepare invoke.
         * @param params        the params which method invoke need.
         * @return
         */
        Object executeAfterMethod(@Nullable Object result,
                                  Class<?> proxyClass, Object invoker, Method method, Object... params) throws Throwable ;
    }


    public interface ExceptionMethodHandleLogic<Error extends Throwable>{
        /**
         * Support an FunctionInterface,wu can define special exception method processor's logic,
         * through this export point,we can get method's invoke infos,virtual invoker,invoke result,and proxy's virtual reference.
         * Required: current exception handle exist as  Systems or Global exception handle, not throwing exception in method body!
         * @param error         error info's abstract inst.
         * @param result        this method's invoked result.
         * @param proxyClass    proxy's virtual reference
         * @param invoker       virtual invoker,spring beanDefinition or other beanDefinitions came from other framework,
         *                      contains original features.
         * @param method        the method which prepare invoke.
         * @param params        the params which method invoke need.
         * @return
         */
        Object handleMethodException(@NotNull Error error, @Nullable Object result,
                                     Class<?> proxyClass, Object invoker, Method method, Object... params) throws Throwable;

        /**
         * Return errorType of supported, Systems and Globals exception handle will return null,
         * at the same time, processor will executable all system error, user's biz exception
         * required developer supplied prefer Error for your biz, such as AuthenticationException,
         * @return
         */
        Class<Error> support();
    }

    /**
     *  After construct before reference invoke. used for register logic stronger chain.
     */
    public void postConstruct(){
        // do Nothing.
    };

    public List<BeforeMethodLogic> getBeforeMethodLogicChain() {
        return beforeMethodLogicChain;
    }

    public List<AfterMethodLogic> getAfterMethodLogicChain() {
        return afterMethodLogicChain;
    }

    public List<ExceptionMethodHandleLogic> getExceptionMethodHandleLogicChain() {
        return exceptionMethodHandleLogicChain;
    }


    public void addBeforeMethodLogic(BeforeMethodLogic... beforeMethodLogic) {
        this.beforeMethodLogicChain.addAll(Arrays.asList(beforeMethodLogic));
    }

    public void addBeforeMethodLogic(List<BeforeMethodLogic> beforeMethodLogic) {
        this.beforeMethodLogicChain.addAll(beforeMethodLogic);
    }

    public void addAfterMethodLogic(AfterMethodLogic... afterMethodLogic) {
        this.afterMethodLogicChain.addAll(Arrays.asList(afterMethodLogic));
    }
    public void addAfterMethodLogic(List<AfterMethodLogic> afterMethodLogic) {
        this.afterMethodLogicChain.addAll(afterMethodLogic);
    }

    public void addExceptionMethodHandleLogic(ExceptionMethodHandleLogic... exceptionMethodHandleLogic) {
        this.exceptionMethodHandleLogicChain.addAll(Arrays.asList(exceptionMethodHandleLogic));
    }

    public void addExceptionMethodHandleLogic(List<ExceptionMethodHandleLogic> exceptionMethodHandleLogic) {
        this.exceptionMethodHandleLogicChain.addAll(exceptionMethodHandleLogic);
    }

    public <T extends StrongerAdvice> void referenceExistStrongerLogic(T existStrongerAdvice){
        if(existStrongerAdvice != null){
            addBeforeMethodLogic(existStrongerAdvice.beforeMethodLogicChain);
            addAfterMethodLogic(existStrongerAdvice.afterMethodLogicChain);
            addExceptionMethodHandleLogic(existStrongerAdvice.exceptionMethodHandleLogicChain);
        }
    }
}



